{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Contrastive Explanations Method (CEM) applied to MNIST"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Contrastive Explanation Method (CEM) can generate black box model explanations in terms of pertinent positives (PP) and pertinent negatives (PN). For PP, it finds what should be minimally and sufficiently present (e.g. important pixels in an image) to justify its classification. PN on the other hand identify what should be minimally and necessarily absent from the explained instance in order to maintain the original prediction.\n",
    "\n",
    "The original paper where the algorithm is based on can be found on [arXiv](https://arxiv.org/abs/1802.07623)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "Note\n",
    "    \n",
    "To enable support for the Contrastive Explanation Method, you may need to run\n",
    "    \n",
    "```bash\n",
    "pip install alibi[tensorflow]\n",
    "```\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TF version:  2.2.0\n",
      "Eager execution enabled:  False\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "os.environ[\"TF_USE_LEGACY_KERAS\"] = \"1\"\n",
    "\n",
    "import tensorflow as tf\n",
    "tf.get_logger().setLevel(40) # suppress deprecation messages\n",
    "tf.compat.v1.disable_v2_behavior() # disable TF2 behaviour as alibi code still relies on TF1 constructs\n",
    "import tensorflow.keras as keras\n",
    "from tensorflow.keras import backend as K\n",
    "from tensorflow.keras.layers import Conv2D, Dense, Dropout, Flatten, MaxPooling2D, Input, UpSampling2D\n",
    "from tensorflow.keras.models import Model, load_model\n",
    "from tensorflow.keras.utils import to_categorical\n",
    "\n",
    "import matplotlib\n",
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import os\n",
    "from alibi.explainers import CEM\n",
    "\n",
    "print('TF version: ', tf.__version__)\n",
    "print('Eager execution enabled: ', tf.executing_eagerly()) # False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load and prepare MNIST data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x_train shape: (60000, 28, 28) y_train shape: (60000,)\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
    "print('x_train shape:', x_train.shape, 'y_train shape:', y_train.shape)\n",
    "plt.gray()\n",
    "plt.imshow(x_test[4]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Prepare data: scale, reshape and categorize"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x_train shape: (60000, 28, 28, 1) x_test shape: (10000, 28, 28, 1)\n",
      "y_train shape: (60000, 10) y_test shape: (10000, 10)\n"
     ]
    }
   ],
   "source": [
    "x_train = x_train.astype('float32') / 255\n",
    "x_test = x_test.astype('float32') / 255\n",
    "x_train = np.reshape(x_train, x_train.shape + (1,))\n",
    "x_test = np.reshape(x_test, x_test.shape + (1,))\n",
    "print('x_train shape:', x_train.shape, 'x_test shape:', x_test.shape)\n",
    "y_train = to_categorical(y_train)\n",
    "y_test = to_categorical(y_test)\n",
    "print('y_train shape:', y_train.shape, 'y_test shape:', y_test.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "xmin, xmax = -.5, .5\n",
    "x_train = ((x_train - x_train.min()) / (x_train.max() - x_train.min())) * (xmax - xmin) + xmin\n",
    "x_test = ((x_test - x_test.min()) / (x_test.max() - x_test.min())) * (xmax - xmin) + xmin"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define and train CNN model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cnn_model():\n",
    "    x_in = Input(shape=(28, 28, 1))\n",
    "    x = Conv2D(filters=64, kernel_size=2, padding='same', activation='relu')(x_in)\n",
    "    x = MaxPooling2D(pool_size=2)(x)\n",
    "    x = Dropout(0.3)(x)\n",
    "    \n",
    "    x = Conv2D(filters=32, kernel_size=2, padding='same', activation='relu')(x)\n",
    "    x = MaxPooling2D(pool_size=2)(x)\n",
    "    x = Dropout(0.3)(x)\n",
    "    \n",
    "    x = Conv2D(filters=32, kernel_size=2, padding='same', activation='relu')(x)\n",
    "    x = MaxPooling2D(pool_size=2)(x)\n",
    "    x = Dropout(0.3)(x)\n",
    "    \n",
    "    x = Flatten()(x)\n",
    "    x = Dense(256, activation='relu')(x)\n",
    "    x = Dropout(0.5)(x)\n",
    "    x_out = Dense(10, activation='softmax')(x)\n",
    "    \n",
    "    cnn = Model(inputs=x_in, outputs=x_out)\n",
    "    cnn.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])\n",
    "    \n",
    "    return cnn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_1 (InputLayer)         [(None, 28, 28, 1)]       0         \n",
      "_________________________________________________________________\n",
      "conv2d (Conv2D)              (None, 28, 28, 64)        320       \n",
      "_________________________________________________________________\n",
      "max_pooling2d (MaxPooling2D) (None, 14, 14, 64)        0         \n",
      "_________________________________________________________________\n",
      "dropout (Dropout)            (None, 14, 14, 64)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_1 (Conv2D)            (None, 14, 14, 32)        8224      \n",
      "_________________________________________________________________\n",
      "max_pooling2d_1 (MaxPooling2 (None, 7, 7, 32)          0         \n",
      "_________________________________________________________________\n",
      "dropout_1 (Dropout)          (None, 7, 7, 32)          0         \n",
      "_________________________________________________________________\n",
      "conv2d_2 (Conv2D)            (None, 7, 7, 32)          4128      \n",
      "_________________________________________________________________\n",
      "max_pooling2d_2 (MaxPooling2 (None, 3, 3, 32)          0         \n",
      "_________________________________________________________________\n",
      "dropout_2 (Dropout)          (None, 3, 3, 32)          0         \n",
      "_________________________________________________________________\n",
      "flatten (Flatten)            (None, 288)               0         \n",
      "_________________________________________________________________\n",
      "dense (Dense)                (None, 256)               73984     \n",
      "_________________________________________________________________\n",
      "dropout_3 (Dropout)          (None, 256)               0         \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 10)                2570      \n",
      "=================================================================\n",
      "Total params: 89,226\n",
      "Trainable params: 89,226\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "cnn = cnn_model()\n",
    "cnn.summary()\n",
    "cnn.fit(x_train, y_train, batch_size=64, epochs=5, verbose=1)\n",
    "cnn.save('mnist_cnn.h5', save_format='h5')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Evaluate the model on test set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test accuracy:  0.9871\n"
     ]
    }
   ],
   "source": [
    "cnn = load_model('mnist_cnn.h5')\n",
    "score = cnn.evaluate(x_test, y_test, verbose=0)\n",
    "print('Test accuracy: ', score[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define and train auto-encoder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ae_model():\n",
    "    x_in = Input(shape=(28, 28, 1))\n",
    "    x = Conv2D(16, (3, 3), activation='relu', padding='same')(x_in)\n",
    "    x = Conv2D(16, (3, 3), activation='relu', padding='same')(x)\n",
    "    x = MaxPooling2D((2, 2), padding='same')(x)\n",
    "    encoded = Conv2D(1, (3, 3), activation=None, padding='same')(x)\n",
    "    \n",
    "    x = Conv2D(16, (3, 3), activation='relu', padding='same')(encoded)\n",
    "    x = UpSampling2D((2, 2))(x)\n",
    "    x = Conv2D(16, (3, 3), activation='relu', padding='same')(x)\n",
    "    decoded = Conv2D(1, (3, 3), activation=None, padding='same')(x)\n",
    "\n",
    "    autoencoder = Model(x_in, decoded)\n",
    "    autoencoder.compile(optimizer='adam', loss='mse')\n",
    "    \n",
    "    return autoencoder"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model_1\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_2 (InputLayer)         [(None, 28, 28, 1)]       0         \n",
      "_________________________________________________________________\n",
      "conv2d_3 (Conv2D)            (None, 28, 28, 16)        160       \n",
      "_________________________________________________________________\n",
      "conv2d_4 (Conv2D)            (None, 28, 28, 16)        2320      \n",
      "_________________________________________________________________\n",
      "max_pooling2d_3 (MaxPooling2 (None, 14, 14, 16)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_5 (Conv2D)            (None, 14, 14, 1)         145       \n",
      "_________________________________________________________________\n",
      "conv2d_6 (Conv2D)            (None, 14, 14, 16)        160       \n",
      "_________________________________________________________________\n",
      "up_sampling2d (UpSampling2D) (None, 28, 28, 16)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_7 (Conv2D)            (None, 28, 28, 16)        2320      \n",
      "_________________________________________________________________\n",
      "conv2d_8 (Conv2D)            (None, 28, 28, 1)         145       \n",
      "=================================================================\n",
      "Total params: 5,250\n",
      "Trainable params: 5,250\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "ae = ae_model()\n",
    "ae.summary()\n",
    "ae.fit(x_train, x_train, batch_size=128, epochs=4, validation_data=(x_test, x_test), verbose=0)\n",
    "ae.save('mnist_ae.h5', save_format='h5')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Compare original with decoded images"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 1440x288 with 10 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "ae = load_model('mnist_ae.h5')\n",
    "\n",
    "decoded_imgs = ae.predict(x_test)\n",
    "n = 5\n",
    "plt.figure(figsize=(20, 4))\n",
    "for i in range(1, n+1):\n",
    "    # display original\n",
    "    ax = plt.subplot(2, n, i)\n",
    "    plt.imshow(x_test[i].reshape(28, 28))\n",
    "    ax.get_xaxis().set_visible(False)\n",
    "    ax.get_yaxis().set_visible(False)\n",
    "    # display reconstruction\n",
    "    ax = plt.subplot(2, n, i + n)\n",
    "    plt.imshow(decoded_imgs[i].reshape(28, 28))\n",
    "    ax.get_xaxis().set_visible(False)\n",
    "    ax.get_yaxis().set_visible(False)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generate contrastive explanation with pertinent negative"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Explained instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "idx = 15\n",
    "X = x_test[idx].reshape((1,) + x_test[idx].shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.imshow(X.reshape(28, 28));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Model prediction:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(5, 0.99959975)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cnn.predict(X).argmax(), cnn.predict(X).max()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "CEM parameters:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "mode = 'PN'  # 'PN' (pertinent negative) or 'PP' (pertinent positive)\n",
    "shape = (1,) + x_train.shape[1:]  # instance shape\n",
    "kappa = 0.  # minimum difference needed between the prediction probability for the perturbed instance on the\n",
    "            # class predicted by the original instance and the max probability on the other classes \n",
    "            # in order for the first loss term to be minimized\n",
    "beta = .1  # weight of the L1 loss term\n",
    "gamma = 100  # weight of the optional auto-encoder loss term\n",
    "c_init = 1.  # initial weight c of the loss term encouraging to predict a different class (PN) or \n",
    "              # the same class (PP) for the perturbed instance compared to the original instance to be explained\n",
    "c_steps = 10  # nb of updates for c\n",
    "max_iterations = 1000  # nb of iterations per value of c\n",
    "feature_range = (x_train.min(),x_train.max())  # feature range for the perturbed instance\n",
    "clip = (-1000.,1000.)  # gradient clipping\n",
    "lr = 1e-2  # initial learning rate\n",
    "no_info_val = -1. # a value, float or feature-wise, which can be seen as containing no info to make a prediction\n",
    "                  # perturbations towards this value means removing features, and away means adding features\n",
    "                  # for our MNIST images, the background (-0.5) is the least informative, \n",
    "                  # so positive/negative perturbations imply adding/removing features"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Generate pertinent negative:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# initialize CEM explainer and explain instance\n",
    "cem = CEM(cnn, mode, shape, kappa=kappa, beta=beta, feature_range=feature_range, \n",
    "          gamma=gamma, ae_model=ae, max_iterations=max_iterations, \n",
    "          c_init=c_init, c_steps=c_steps, learning_rate_init=lr, clip=clip, no_info_val=no_info_val)\n",
    "\n",
    "explanation = cem.explain(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pertinent negative:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Pertinent negative prediction: 3\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "print(f'Pertinent negative prediction: {explanation.PN_pred}')\n",
    "plt.imshow(explanation.PN.reshape(28, 28));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generate pertinent positive"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "mode = 'PP'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# initialize CEM explainer and explain instance\n",
    "cem = CEM(cnn, mode, shape, kappa=kappa, beta=beta, feature_range=feature_range, \n",
    "          gamma=gamma, ae_model=ae, max_iterations=max_iterations, \n",
    "          c_init=c_init, c_steps=c_steps, learning_rate_init=lr, clip=clip, no_info_val=no_info_val)\n",
    "\n",
    "explanation = cem.explain(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pertinent positive:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Pertinent positive prediction: 5\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "print(f'Pertinent positive prediction: {explanation.PP_pred}')\n",
    "plt.imshow(explanation.PP.reshape(28, 28));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Clean up:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "os.remove('mnist_cnn.h5')\n",
    "os.remove('mnist_ae.h5')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
